let age: number = 19

let myName:string = 'zs'

let isLoading:boolean = true

let a:null = null

let b:undefined = undefined

let s:symbol = Symbol()



// 数组类型的2种写法

let numbers: number[] = [1,3,5]

let strings: Array<string> = ['asd', 'zxc', 'qwe']


// 联合类型的写法
// 括号中既有number类型又有string类型的数据
let arr: (number | string)[] = ['asd',123,'zc']

// number类型, 或string类型的数组
let arr1: number | string [] = 1

let arr2: number | string [] = ['zxc','qwe','aaa']


// 类型别名: 为任意类型起别名
// 使用场景: 当同一类型(复杂)被多次使用时, 可以通过类型别名, 简化该类型的使用

type nsArray = (number|string)[]

let arr3:nsArray = [1,2,4,'asd']

type obj = {
  name: string,
  age: number
}

let p:obj = {
  name: 'asd',
  age: 19
}

//函数类型
// 1.单独指定参数,返回值的类型
function add (n1:number, n2:number): number {
  return n1+n2
}

const add1 = (n1:number, n2:number):number => {
  return n1+n2
}

//2.同时指定参数和返回值类型
// 解释: 当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型
// 注意: 这种类型只适合用于函数表达式
const add3: (n1:number,n2:number) => number = (n1,n2) => {
  return n1+n2
}


//对象类型
// 当一个对象被多次使用,一般会使用接口(interface)来描述对象的类型,打到复用的目的
let person: {name:string,age:number,sayHi(word:string):void} = {
  name: 'zx',
  age: 20,
  sayHi (word) {
    console.log(word)
  }
}

person.sayHi('hello')

//接口
interface IPerson {
  name:string,
  age: number,
  sayHi(word:string) :void
}

let personI:IPerson = {
  name: 'asd',
  age: 20,
  sayHi(word:string) {
    console.log(word)
  }
}


// 接口继承 extends
// 如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用

// interface Point2D {
//   x: number,
//   y: number
// }

// interface Point3D extends Point2D {
//   z: string
// }

// let point3:Point3D = {
//   x: 3,
//   y: 5,
//   z: 'asd'
// }

// 元祖(Tuple)
// 另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对应的类型
// number[]的缺点: 不严谨,因为该类型的数组中可以出现任意多个数字

let position: [number, number] = [39.1144, 55.1123]

let position2: [string, number] = ['asd', 22.1232]


//类型推论
// 在TS中,某些没有明确指出类型的地方,TS的类型推论机制会帮助提供类型(类型注解可以省略不写)
//如果声明变量没有立即初始化值,此时,还必须手动添加类型注解
// let a
// a = 19
// a = 'tom'


//类型断言
// 1.使用as关键词实现类型断言
// 2.关键字as后面的类型是一个更加具体的类型
// 3.通过类型断言,link的类型变得更加具体,这样就可以访问到a标签特有的属性或方法了
// 技巧:
//   console.dir()打印dom元素,在属性列表的最后面,可以看到该元素的类型


// const link = document.getElementById('link')
// link.href  // 不存在属性href
// const link = document.getElementById('link') as HTMLAnchorElement


//字面量类型
let str1 = 'hello' //string类型
const str2 = 'hello' //hello类型
const str3: 'hello' = 'hello'

/**
 * str1是一个变量(let),它的值是任意字符串,所以类型为:string
 * str2是一个常量(const),它的值不能变化,只能是hello, 所以它的类型为hello
 * 
 * 
 * 此处str2的 hello 就是一个字面量类型,也就是说某个特定的字符串可作为TS中的类型
 * 除了字符串外,任意的js字面量(比如,对象,数字等)都可以作为类型使用
 * 
 * 使用模式: 字面量类型配合联合类型一起使用
 * 使用场景: 用来表示一组明确的可选值列表
 * 
 */

function changeDirection(direction: 'up' | 'right' | 'down' | 'left') {
  console.log(direction)
}

changeDirection('up')


// 枚举: 定义一组命名常量,它描述一个值,该值可以是这些命名常量中的一个
// 枚举的功能类似于 字面量类型+联合类型 组合的功能, 也可以表示一组明确的可选值

enum Direction {
  Up,
  Down,
  Left,
  Right
}

function changeDirection1(direction: Direction) {
  console.log(direction) // 0 1 2 3 默认从0开始递增
}

changeDirection1(Direction.Down) // 1

// 枚举成员的值 以及 数字枚举
// 枚举成员的值为数字的枚举, 称为: 数字枚举

enum Dir1 {a=10, b, c, d}  // 10 11 12 13

enum Dir2 { a=10, v='asd', c= 55}


// 字符串枚举
// 字符串枚举: 枚举成员的值是字符串
// 注意: 字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值

enum Dir3 { 
  Up = 'asd',
  Right = 'aaa',
  Down = 'zxc',
  Left = 'qqq'
}


enum Dir4 {Up1, Dow1n='cat', Cla1='jerry'}


// typeof 运算符
// 众所周知,js中提供了typeof操作符,用来在js中获取数据的类型
// console.log(typeof 'Hello world') // string

// ts中的typeof: 可以在类型上下文中引用变量或属性的类型(类型查询)
// 1.使用 typeof   获取变量P的类型,结果与第一种(对象字面量形式的类型)相同
// 2.typeof 出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文(区别于js代码)
// 3.typeof 只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)


let tp = {x:1, y:2}

function formatP(point: typeof tp) {
  console.log(111)
}

function formatP11 (point: {x:number, y:number}) {
  console.log(111)
}

formatP({x:1, y:2})

let num: typeof tp.x

function addt(n1:number, n2:number) {
  return n1+n2
}

// let ret: typeof add(1,2) //无法查询




// ts的高级类型
/**
 * 1.class类
 * 2.类型兼容型
 * 3.交叉类型
 * 4.泛型和keyof   ****
 * 5.索引签名类型 和 索引查询类型
 * 6.映射类型
 */


/**
 * class类型
 * 
 * 1.根据ts中的推论,可以知道Person类的实例对象P的类型是Person
 * 2.ts中的class,不仅提供了class的语法功能,也作为一种类型存在
 */

class Person {
  //实例属性初始化
  age: number
  gender = '男'
  // gender:string = '男'
}

const pc = new Person()

pc.age

// class的构造函数
class Personp {
  age: number
  name: string
  constructor(age:number,name:string) {
    this.age = age
    this.name = name
  }
  salary:number = 19
}

const pa = new Personp(19,'tom')
console.log(pa.name)


// 类的继承方式
// 1.extends(继承父类)
// 2.implements(实现接口)


// 1.extends(继承父类) 类和类之间的关系
class Animal {
  move () {
    console.log('move')
  }
}

class Dog extends Animal {
  brak () {
    console.log('brak')
  }
}

const d = new Dog()
d.move() // d会继承父类的方法
d.brak()


// 2.implements(实现接口) ts特有的方式 类和接口之间的关系 类中必须提供接口中的所有属性和方法

interface Singable {
  sing (): void
  name: string
}

class Person2 implements Singable {
  sing () {
    console.log('sing')
  }
  // name: string
  name = 'jack'
}

const p4 = new Person2()
// p4.sing()


// class类的可见性修饰符
// 1. public: 表示公有的,公开的,共有成员可以被任何地方访问,默认可见性
// 2. protected: 表示受保护的, 仅对其声明所在类和子类中(非实例对象)可见
// 3. private: 表示私有的, 只在当前类中可见,对实例对象以及子类也是不可见的


// 1. public: 表示公有的,公开的,共有成员可以被任何地方访问,默认可见性
// class Animal {
//   public move () {
//       console.log(111)
//   }
//   run() {
//       this.move()
//   }
// }

// const a = new Animal()
// a.move() 

// class Dog extends Animal {
//   bark() {
//       this.move()
//   }
// }


// const d = new Dog()

// d.move()







// 2. protected: 表示受保护的, 仅对其声明所在类和子类中(非实例对象)可见
// class Animal {
//   protected move () {
//       console.log(111)
//   }
//   run() {
//       this.move()
//   }
// }

// const a = new Animal()
// a.move() //不可用

// class Dog extends Animal {
//   bark() {
//       this.move()
//   }
// }


// const d = new Dog()

// d.move() // 不可用










// 3. private: 表示私有的, 只在当前类中可见,对实例对象以及子类也是不可见的
// class Animal {
//   private __move__ () {
//       console.log('Animal 内部辅助函数')
//   }
//   protected run() {
//       this.__move__() 
//   }
// }

// const a = new Animal()
// a.__move__() //不可用

// class Dog extends Animal {
//   bark() {
// this.__move__() //不可见
//   }
// }


// const d = new Dog()

// d.__move__() // 不可用


// readonly修饰符
// readonly: 表示只读,用来防止在构造函数外对属性进行赋值
// 1. 使用readonly 关键字修饰该属性是只读的,注意不能修饰方法
// 2. 属性age后面的类型注解(:number), 则age的类型为18(字面量类型)
// 3. 接口或者{}表示的对象类型,也可以用readonly



// class Person {
//   // readonly age = 18 //字面量类型
//   //只要是readonly修饰的属性,必须手动提供明确的类型
//   readonly age: number = 18 //默认值
//   constructor(age:number) {
//       this.age = age
//   }
//   //不能修饰方法
//   // readonly setAge () {
//   //     // this.age = 20 //无法修改
//   // }
// }

// let p = new Person(20) //默认值是18,加了变成20
// // p.age = 33 //无法修改


// interface IPerson {
//   readonly name: string
// }

// let obj2: IPerson = {
//   name: 'tom'
// }
// // obj.name = 'cat' // 无法修改




// let obj: {readonly name:string} = {
//   name: 'tom'
// }





// 类型兼容性

class Pointss {
  x:number
  y:number
}

class Pointss2 {
  x:number
  y:number
}

const pss:Pointss = new Pointss2()
/**
 * Pointss 和 Pointss2 是两个名称不同的类
 * 变量pss的类型被显示标注为Pointss类型, 但它的值却是 Pointss2的实例,且没有类型错误
 * 因为ts是结构化类型系统,只检查Pointss和Pointss2的结构是否相同(相同,都具有xy两个属性,属性类型也相同)
 * 
 * 对于对象类型来说,y的成员至少与x相同(y>=x),则x兼容y(成员多的可以赋值给成员少的)
 */



// 函数之间的类型兼容性
// 需要考虑
// 1.参数个数
// 2.参数类型
// 3.返回值类型


/**
 * 1.参数个数
 * 参数少的可以赋值给参数多的,
 * 在js中省略用不到的参数实际上很常见,这样的使用方式,促成了ts中函数类型之间的兼容性
 */

// type F1 = (a:number) => void
// type F2 = (a:number, b:number) => void

// let f1: F1
// let f2: F2

// f2 = f1

// f1 = f2 错误演示



/**
 * 2.参数类型
 * 此处与前面讲到的接口兼容性冲突
 * 技巧: 将对象拆开, 把每个属性看做一个个参数,则参数少的可以赋值给参数多的
 * 
 */

// interface Point2D {
//   x:number,
//   y:number
// }

// interface Point3D {
//   x:number,
//   y:number,
//   z:number
// }

// type F2 = (p: Point2D) => void

// type F3 = (p: Point3D) => void

// let f2:F2
// let f3:F3

// f3 = f2
// f2 = f3 错误示范



/**
 * 2.返回值类型
 * 
 * 如果返回值类型是原始类型,此时两个类型要相同,
 * 
 * 如果返回值类型是对象类型,此时成员多的可以赋值给成员少的
 * 
 */

// type F7 = () => {name: string}

// type F8 = () => {name:string,age:number}

// let f7:F7

// let f8:F8

// f7 = f8
// f8 = f7 错误示范




// 交叉类型( & )
// 功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)



interface Personps {
  name: string
}

interface Contact {
  phone: string
}

type PersonDetail = Personps & Contact
let objp: PersonDetail = {
  name: 'asd',
  phone: 'asdasd'
}

/**
 * 交叉类型和接口类型的对比
 * 相同点: 都可以实现对象类型的组合
 * 不同点: 两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同
 * 
 */

// interface A {
//   fn: (a:number) => string
// }

// interface B extends A {
//   fn: (a:string) =>  string
// }
// 类型不兼容



// interface A {
//   fn: (a:number) => string
// }

// interface B {
//   fn: (a:string) =>string
// }
// type C = A & B

// fn: (a: string |  number) => string





// 泛型
/**
 * 保证类型安全前提下,让函数等多种类型一起工作,从而实现复用, 常用于: 函数,接口,class中
 * 
 * 1.在函数名称后面添加 <>, <>中添加类型变量
 * 
 * 2.类型变量Type,是一种特殊类型的变量,它处理类型而不是值
 * 
 * 3.该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)
 * 
 * 4.因为Type是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
 * 
 * 5.类型变量Type,可以是任意合法的变量名称
 * 
 * 
 */


function id<Type> (a:Type):Type {
  return a
}

const id2 = function<Type> (a:Type):Type {
  return a
}

const id3 = <Type>(a:Type):Type => { return a}

// 调用泛型函数
const num3 = id3<number>(10)

// 简化调用 会自动推断类型
const num4 = id3(10)



/**
 * 泛型约束: 默认情况下,泛型函数的类型Type可以代表多个类型,这导致无法访问任何属性
 * 此时,就需要为泛型添加约束来收缩类型(缩小类型取值范围)
 * 
 *  1. 指定更加具体的类型
 *  2. 添加约束
 * 
 */

// 1.指定更加具体的类型

function iddd<Type>(value:Type): Type {
  // console.log(value.length) //不存在length属性,无法访问
  return value
}

//比如,将类型修改为Type[](Type类型的数组),因为只要是数组就一定存在length属性
const idd = <Type>(arr:Type[]) => {
  console.log(arr.length)
}


//2.添加约束
interface ILength { length:number }

const idddd = <Type extends ILength>(arr:Type) => {
  //Type要满足具有length属性这个约束
  console.log(arr.length)
}

// idddd(3) // 传入参数必须要有length这个属性

idddd([1,2,3])
idddd('tom')

//1.创建描述约束的接口Length,该接口要求提供length属性
//2.通过extends关键字使用该接口,为泛型(类型变量)添加约束
//3.该约束表示:传入的类型必须具有length属性
//注意:传入的实参(比如,数组)只要有length属性即可,这也符合前面讲到的接口的类型兼容性



// 多个泛型变量的情况
// 泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)

// keyof: 接收一个对象类型,生成其键名称(可能是字符串或数字的)联合类型 'name'|'age'

const getProp = <Type, Key extends keyof Type>(obj:Type, key:Key) => {
  return obj[key]
}

let personp = {
  name: 'asd',
  age: 19
}

getProp(personp, 'age')


/**
 * 泛型接口: 接口也可以配合泛型来使用,以增加其灵活性,复用性
 * 
 * 1. 在接口名称的后面添加<类型变量>,那么,这个接口就变成了泛型接口
 * 
 * 2. 接口的类型变量,对接口中其他成员可见, 也就是接口中所有成员都可以使用类型变量
 * 
 * 3. 使用泛型接口时,需要显示指定具体的类型(idFun<number>)
 * 
 * 4. 此时, id方法的参数和返回值类型都是number, ids方法的返回值类型是 number[]
 * 
 */


interface IdFunc<Type> {
  id: (a:Type) => Type,
  ids: () => Type[]
}

//调用泛型接口时,一定要提供一个类型参数
let obj:IdFunc<number> = {
  id (value) {
    return value
  },
  ids () {
    return [1,3,5]
  }
}



/**
 *  泛型工具类型: TS内置了一些常用的工具类型,来简化TS中的一些常见操作
 *  说明:他们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。
 * 
 *  1.Partial<Type>
 *  2.Readonly<Type>
 *  3.Pick<Type, Keys>
 *  4.Reord<Keys, Type>
 */


//1.Partial<Type>用来构造(创造)一个类型,将Type的所有属性设置为可选
//构造出来的新类型PartialProps结构和Props相同,但所有属性都变为可选的

interface Props {
  id: string,
  children: number[]
}

type PartialProps = Partial<Props>

let p1:PartialProps = {
  id: '1'
}


//2.泛型工具类型-Readonly<Type>用来构造一个类型,将Type的所有属性都设置为readonly(只读)
//构造出来的新类型ReadonlyProps和Props结构相同,但所有属性都变为只读的。
//当我们想重新给id属性赋值时,就会报错,无法分配到id,因为它是只读属性


type ReadonlyProps = Readonly<Props>

let p2:ReadonlyProps = {
  id: '123',
  children: [1,4,5]
}

// p2.id = '13' 无法修改




//3.泛型工具类型 Pick<Type,Keys> 从Type中选择一组属性来构造新类型
// 1.Pick工具类型有两个类型变量: 1.表示选择谁的属性 2.表示选择哪几个属性
// 2.其中第二个类型变量,如果只选择一个,则只传入该属性名即可
// 3.第二个类型变量传入的属性只能是第一个类型变量中存在的属性
// 4.构造出来的新类型PickProps,只有id和title2个属性
interface Props1 {
  id: number,
  title: string,
  children: number[]
}

type PickProps = Pick<Props1, 'id'|'title'>
type PickProps2 = Pick<Props1, 'id'>
let p3:PickProps = {
  id: 1,
  title: 'asd'
}



//4.泛型工具类型 Record<Keys,Type>构造一个对象类型,属性键为Keys,属性类型为Type
// type RecordObj = {
//     a:string,
//     b:string,
//     c:string
// }

type RecordObj = Record<'a'|'b'|'c', string>

let p44:RecordObj = {
  a: '1',
  b: '2',
  c: '3'
}



/**
 * 索引签名类型
 * 当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时,就需要索引签名类型了
 * 
 * 1.使用[key:string] 来约束接口中允许出现的名称。表示只要是string类型的属性名称,都可以出现在对象中
 *  
 * 2.这样,obj中就可以出现多个属性(a,b,c)
 * 
 * 3.key只是一个占位符,可以切换成任意合法的变量名称
 * 
 * 4.隐藏的前置知识,js中对象({})的键是string 类型的
 * 
 * 
 */

interface AnyObject {
  [key:string]: number
}

let objsss:AnyObject = {
  a: 1,
  b: 2
}

/**
 * 在js中数组也是一组特殊的对象,特殊在 数组的键(索引)是数值类型
 * 并且,数组也可出现任意多个元素。所以,在数组对应的泛型接口中,也用到了索引签名类型
 */

const arrss = [1,3,5]

interface MyArray<T> {
  [n:number]: T
}

let arrr:MyArray<number> = [1,3,4]

//1.MyArray接口模拟原生的数组接口,并使用[n:number]来作为索引签名类型
//2.该索引签名表示: 只要是number类型的键(索引)都可以出现在数组中,或者说数组中可以有任意多个元素
//3.同时也符合数组索引是number类型这一前提






/**
 * 映射类型: 基于旧类型创建新类型(对象类型),减少重复,提升开发效率
 */

// 比如: 类型PropKeys中有 x/y/z, 另一个类型 Type1中也有x/y/z,并且Type1中 x/y/z的类型相同
type PropsKeys = 'x'|'y'|'z'

type Type1 = {x:number;y:number;z:number}

//简化
// key in PropsKeys表示 Key可以是PropsKeys联合类型中的任意一个,类似于forin(let k in obj)
type Type2 = {[key in PropsKeys]:number}

/**
 * 解释
 * 1.映射类型是基于索引签名类型的,所以,该语法类似索引签名类型,也用了 []
 * 2.Key in PropKeys 表示Key可以是PropKeys联合类型中任意一个,类似于 forin(let k in obj)
 * 3.使用映射类型创建的新对象类型Type2和Type1结构完全相同
 * 4.注意: 映射类型只能在类型别名中使用,不能在接口中使用
 */



//根据对象来创建
// 1.执行 keyof Props 拿到对象类型Props中的所有键的联合类型, 'a'|'b'|'c'
// 2.Key in 代表是拿到的 abc中人任意一个,即Props所有的键名中的任意一个
type Propss = {a:number;b:string;c:boolean}

type Type3 = {[Key in keyof Propss]:number}




//Partial原理 

type Propsa = {a:number;b:string;c:boolean}

type MyPartial<T> = {
  [P in keyof T]?: T[P]
  // \?表示可选可不选
  // keyof T => 'a'|'b'|'c'
  // P in keyof T => 可以是 'a'|'b'|'c'任意一个
  // T[P] => Props[a], Props[b], Props[c]
}



/**
 * 刚刚用到的T[P],在ts中叫做 索引查询类型
 * 用来查询类属性的类型
 */

// type Props = {
//   a:number
//   b:string
//   c:boolean
// }


// type TypeA = Props['a']
// // type TypeAs = Props['d'] //d不存在
// //Props['a']表示查询类型Props中属性'a'对应的类型number,所以,TypeA的类型为number
// //注意: []中的属性必须存在于被查询类型中,否则报错


// // keyof获取对象中所有的键
// // keyof一般获取某种类型的键，直接返回联合类型
// // keyof后面跟一种类型，不能跟具体的值，也常常与typeof一起使用

// type Partial2<T> = {
//   [P in keyof T]?: T[P]  //T[P]=>Props[P], p => Props中任意的一个键
// }
// type PartialProps = Partial2<Props>









// //同时查询多个索引的类型
// type TypeAA = Props['a'|'b'] // string | number 如果a和b都为number,TypeAA=number
// // keyof获取对象中所有的键
// type TypeBB = Props[keyof Props] // string | number | boolean




/**
 * 使用第三方库时,发现它们都有相应的TS类型,这些类型怎么来的？ 类声明文件
 * 
 * 类型声明文件: 用来为已存在的JS库提供类型信息
 * 这样在TS项目中使用这些库时,就像用TS一样,都会有代码提示,类型保护等机制了
 * 
 * 1.TS的两种文件类型
 * 2.类型声明文件的使用说明
 */




/*  1.TS的两种文件类型
        1. ts文件
        2. .d.ts文件


    ts文件
        1. 既包含类型信息又有可执行代码
        2. 可以被编译为js文件,然后执行代码  
        3. 用途: 编写程序代码的地方

    .d.ts文件
        1. 只包含类型信息的类型声明文件
        2. 不会生成js文件,仅用于提供类型信息
        3. 用途: 为js提供类型信息

    总结: .ts是 implementation(代码实现文件)  .d.ts是declaration(类型声明文件)
    如果要为js库提供类型信息, 要使用 .d.ts 文件
*/

















